﻿using System;
using System.Collections.Generic;
using System.Text;
using Modelo.Niveles.Manejo_de_datos;

namespace Modelo
{
    [Serializable]
    public class Mapa
    {
        private int cantPuntosX;
        private int cantPuntosY;
        private int tamCelda;
        private Posicion infoCreacion = null; //para poder reiniciarlo cuando pierde sin leer el archivo.dd
        private Celda[,] grilla;
        private Bombita Jugador = null ;
        private List<IColisionable> Obstaculos = new List<IColisionable>();
        private List<IColisionable> Enemigos = new List<IColisionable>();
        private List<Bloque> bloques = new List<Bloque>();
        private int tamjugador;
        private Posicion Salida = null; //reepresenta las coordenadas de la celda del bloque que lo tama

        private void crearMapa(string[] lineasMapa)
        {
            grilla = new Celda[cantPuntosX / tamCelda, cantPuntosY / tamCelda]; //El tamanio ahora es parametro. [columnas][filas]
            Obstaculos = new List<IColisionable>();
            poblarMapa(lineasMapa);
        }

        private void poblarMapa(string[] lineas)
        {
            for (int i = 0; i < cantPuntosX / tamCelda; i++)
            {
                for (int j = 0; j < cantPuntosY / tamCelda; j++)
                {
                    if (lineas[j][i].ToString() == "0")
                    {
                        grilla[i, j] = new Celda(tamCelda, true);//Por ahora solo asigna como false a las celdas que contendran un bloque.
                    }
                    else if (lineas[j][i].ToString() == "C")
                    {
                        grilla[i, j] = new Celda(tamCelda, true);//Por ahora solo asigna como false a las celdas que contendran un bloque.
                        Enemigo nuevoEnemigo = new Cecilio(new Posicion(i * getTamCelda() + getTamCelda() / 2, j * getTamCelda() + getTamCelda() / 2), 10, this);
                        Obstaculos.Add(nuevoEnemigo);
                    }
                    else
                    {
                        grilla[i, j] = new Celda(tamCelda, false);//Por ahora solo asigna como false a las celdas que contendran un bloque.
                    }
                }
            }
        }
        public int obtenerCeldasX()
        {
            return ( (cantPuntosX + 1) / tamCelda) ;
        }
        public int obtenerCeldasY() {
            return ( (cantPuntosY + 1) / tamCelda);
        }
        public int getTamCelda()
        {
            return tamCelda;
        }

        public int getCantPuntosX()
        {
            return cantPuntosX;
        }

        public int getCantPuntosY()
        {
            return cantPuntosY;
        }

        public Boolean celdaCaminable(int x, int y)
        {
            //Devuelve true o false de acuerdo al valor de caminable de la celda recibida.
            return grilla[x, y].getCaminable();
        }

        public void checkearColision(Bombita jugador)
        {
            List<IColisionable> copiaObstaculos = new List<IColisionable>(Obstaculos);

            foreach (IColisionable obstaculo in copiaObstaculos)
            {
                double distancia = obtenerDistanciaEntre(jugador, obstaculo);
                if( distancia < obstaculo.getTamanio() + jugador.getTamanio())
                {
                    jugador.recibirColision(obstaculo);
                }
            }
            foreach (Enemigo enemigo in Enemigos)
            {
                double distancia = obtenerDistanciaEntre(jugador, enemigo);
                if (distancia < enemigo.getTamanio() + jugador.getTamanio())
                {
                    jugador.recibirColision(enemigo);
                    return; //una vez quese choca con un enemigo se pierde y no se chequea mas nada.
                }
            }
        }

        private double obtenerDistanciaEntre(IColisionable unColisionable, IColisionable otroColisionable)
        {
            //Calcula la distancia entre dos puntos con pitagoras.
            double aux = Math.Pow(unColisionable.getPosX() - otroColisionable.getPosX(), 2);
            aux += Math.Pow(unColisionable.getPosY() - otroColisionable.getPosY(), 2);
            return Math.Sqrt(aux);
        }

        public void reiniciarNivel(Mapa mapa)
        {
           Jugador.setEnMapaYPosicion(mapa,infoCreacion);
        }

        public Celda obtenerCelda(Posicion posicion)
        {
            return grilla[posicion.getX(), posicion.getY()];
        }

        public List<Celda> obtenerCeldasOcupadas() { 
            //devuelve una lista de las celdas que son bloques
            List<Celda> Resultado = new List<Celda>();
            for (int i = 0; i < obtenerCeldasX(); i++)
            {
                for (int j = 0; j < obtenerCeldasY(); j++)
                {
                    if (grilla[i, j].getCaminable() == false) {
                        Resultado.Add(grilla[i, j]);
                    }
                }        
            }

            return Resultado;
        }

        private Celda[,] inicializarCeldas(int cantidadX, int cantidadY,int tamCelda)
        {

            Celda[,] grilla = new Celda[cantidadX , cantidadY ];

            //se hace hasta menos 1 ya que empiezan a contar en 0
            for (int i = 0; i < cantidadX; i++)
            {
                for (int j = 0; j < cantidadY; j++)
                {
                    grilla[i, j] = new Celda(tamCelda, true);
                }
            }
            return grilla;
        }

        public List<Enemigo> verEnemigosAfectados(List<Posicion> posicionesAfectadas)
        {
            List<Enemigo> enemigosAfectados = new List<Enemigo>();
            foreach (Posicion posicion in posicionesAfectadas)
            {
                foreach (Enemigo enemigo in Enemigos)
                {
                    if (Celdas.Obtenercelda(posicion, this) == Celdas.Obtenercelda(new Posicion(enemigo.getPosX(), enemigo.getPosY()), this))
                        enemigosAfectados.Add(enemigo);
                }
            }
            return enemigosAfectados;
        }

        public List<Celda> verCeldasAfectadas(List<Posicion> posicionesAfectadas)
        {
            List<Celda> celdasAfectadas = new List<Celda>();
            foreach (Posicion posicion in posicionesAfectadas)
            {
                Celda celda = Celdas.Obtenercelda(posicion, this);
                if (!celdasAfectadas.Contains(celda))
                    celdasAfectadas.Add(celda);
            }
            return celdasAfectadas;
        }

        public void eliminarEnemigoDestruido(Enemigo enemigo)
        {
            Enemigos.Remove(enemigo);
        }

        public void eliminarBloqueDestruido(Bloque bloque, Celda celda)
        {
            celda.LiberarCelda();//pone en null el ocupante y la marca como caminable
            bloques.Remove(bloque);
        }
        public void UbicarJugador() {
            if (Jugador != null)
            {
                //se verifica excepcion: jugador debe estar seteado
                Jugador.setEnMapaYPosicion(this, infoCreacion);
            }
            else
            {
                throw new Exception("Se esta ubicando un jugador que no esta seteado previamente");
            }
        }
        public List<IColisionable> obtenerObstaculos()
        {
            return Obstaculos;
        }
        public void SetearJugador(Bombita Unjugador) {
            Jugador = Unjugador;
        }

        public Mapa(string path)
        {
            //
            //ESTE ES EL CONSTRUCTOR, LO CARGA DE UN ARCHIVO TXT EXTERNO
            //

            //decalro variables auxiliares
            List<string> infoBloques = new List<string>();
            List<string> infoObstaculos = new List<string>();
            List<string> infoNivel = Archivosdetexto.Obtenercontenido(path);
            List<string> infoEnemigos = new List<string>();
            List<Bloque> Paredes = new List<Bloque>();
            string[] parameters;
            int celdasX = 0;
            int celdasY = 0;

            //valido que la ruta sea valida
            if (infoNivel == null) throw new Exception("La ruta enviada del mapa no es correcta");

            //recorro la infoNivel del nivel
            foreach (string unainfoNivel in infoNivel)
            {
                parameters = unainfoNivel.Split(Convert.ToChar(";"));
                if (parameters[0] == "celda")
                {
                    try
                    {
                        tamCelda = Convert.ToInt32(parameters[1]);
                    }
                    catch (Exception e) { }

                }
                else if (parameters[0] == "tamx")
                {
                    //asigno tamx
                    try
                    {
                        celdasX = Convert.ToInt32(parameters[1]);
                        cantPuntosX = (celdasX * tamCelda) - 1;
                    }
                    catch (Exception)
                    {
                    }
                }
                else if (parameters[0] == "tamy")
                {
                    //asigno tamy
                    try
                    {
                        celdasY = Convert.ToInt32(parameters[1]);
                        cantPuntosY = (celdasY * tamCelda) - 1;
                    }
                    catch (Exception)
                    {
                    }
                }
                else if ((parameters[0] == "cemento") | (parameters[0] == "acero") | (parameters[0] == "ladrillo"))
                {
                    //guardo la info de los bloques para despues agregarlo.
                    infoBloques.Add(unainfoNivel);
                }
                else if ((parameters[0] == "timer") | (parameters[0] == "tole") | (parameters[0] == "habano"))
                {
                    //guardo la info de los powerups para despues agregarlo.
                    infoObstaculos.Add(unainfoNivel);
                }
                else if ((parameters[0] == "cecilio") | (parameters[0] == "lopreg") | (parameters[0] == "lopala"))
                {
                    //guardo la info de los enemigos para despues agregarlo.
                    infoEnemigos.Add(unainfoNivel);
                }
                else if ((parameters[0] == "bombita"))
                {
                    //guardo la info de los enemigos para despues agregarlo.
                    infoCreacion = new Posicion(Convert.ToInt32(parameters[1]), Convert.ToInt32(parameters[2]));
                    tamjugador = Convert.ToInt32(parameters[3]);
                }
                else if ((parameters[0] == "salida"))
                {
                    Salida = new Posicion(Convert.ToInt32(parameters[1]), Convert.ToInt32(parameters[2]));
                }
            }
            //inicializo grilla de celdas con Todas disponibles

            grilla = inicializarCeldas((cantPuntosX + 1) / tamCelda, (cantPuntosY + 1) / tamCelda, tamCelda);

            //inicializo bloques , utilizo reflexion
            Dictionary<string, Type> tiposdebloque = new Dictionary<string, Type>();
            tiposdebloque["acero"] = typeof(BloqueDeAcero);
            tiposdebloque["ladrillo"] = typeof(BloqueDeLadrillo);
            tiposdebloque["cemento"] = typeof(BloqueDeCemento);

            foreach (string unainfoNivel in infoBloques)
            {
                parameters = unainfoNivel.Split(';');
                grilla[Convert.ToInt32(parameters[1]), Convert.ToInt32(parameters[2])] = new Celda(tamCelda, false, tiposdebloque[parameters[0]].GetConstructor(new Type[0]).Invoke(new object[0]) as Bloque);
            }

            //inicializo powerups, utilizo reflexion
            Dictionary<string, Type> tiposdeobstaculos = new Dictionary<string, Type>();
            tiposdeobstaculos["habano"] = typeof(Habano);
            tiposdeobstaculos["tole"] = typeof(ToleTolePowerUp);
            tiposdeobstaculos["timer"] = typeof(Timer);
            Type[] tipoConstuctor = new Type[3];
            tipoConstuctor[0] = typeof(int);
            tipoConstuctor[1] = typeof(Posicion);
            tipoConstuctor[2] = typeof(Mapa);

            foreach (string unainfoNivel in infoObstaculos)
            {
                parameters = unainfoNivel.Split(';');
                Posicion nuevaPos = new Posicion(Convert.ToInt32(parameters[1]), Convert.ToInt32(parameters[2]));
                int tamaniopower = Convert.ToInt32(parameters[3]);
                Obstaculos.Add(tiposdeobstaculos[parameters[0]].GetConstructor(tipoConstuctor).Invoke(new object[3] { tamaniopower, nuevaPos, this }) as IColisionable);
            }

            //inicializo Enemigos, utilizo reflexion
            Dictionary<string, Type> tiposdeEnemigos= new Dictionary<string, Type>();
            tiposdeEnemigos["cecilio"] = typeof(Cecilio);
            tiposdeEnemigos["lopreg"] = typeof(LopezReggae);
            tiposdeEnemigos["lopala"] = typeof(LopezReggaeAlado);
            tipoConstuctor = new Type[3];
            tipoConstuctor[0] = typeof(Posicion);
            tipoConstuctor[1] = typeof(int);
            tipoConstuctor[2] = typeof(Mapa);

            //los tipos del constructor son los mismos para powerup y enemigo ===> se reutiliza
            foreach (string unainfoNivel in infoEnemigos)
            {
                parameters = unainfoNivel.Split(';');
                Posicion nuevaPos = new Posicion(Convert.ToInt32(parameters[1]), Convert.ToInt32(parameters[2]));
                int tamanioEnemigo= Convert.ToInt32(parameters[3]);
                Enemigos.Add(tiposdeEnemigos[parameters[0]].GetConstructor(tipoConstuctor).Invoke(new object[3] { nuevaPos, tamanioEnemigo, this }) as IColisionable);
            }
            
            //chequeo que la salida este tapada por un bloque correcto
            if (Salida != null) {
                Bloque bloqueSalida = grilla[Salida.getX(), Salida.getY()].ObtenerBloque();
                if (bloqueSalida == null) { 
                        throw new Exception("Ocurrio un error con la creacion de la salida");
                }else if((bloqueSalida.GetType() != typeof(BloqueDeLadrillo)) && (bloqueSalida.GetType() != typeof(BloqueDeCemento))){ 
                   throw new Exception("Ocurrio un error con la creacion de la salida");
                }
            }
           
        }
        public bool encontroSalida(){
            if (Salida == null)
            {
                //se controla si el mapa contiene una salida
                return false;
            }
            Posicion posActual = new Posicion (Jugador.getPosX(),Jugador.getPosY());
            if (Celdas.Obtenercelda(posActual, this) == grilla[Salida.getX(), Salida.getY()] && (Enemigos.Count == 0) && posActual != null)
                return true;
            return false;
        }
        public List<IColisionable> obtenerEnemigos() {
            return Enemigos;
        }
        public Bombita obtenerJugador(){
            return Jugador;
        }

        public Posicion obtenerPosicionCentroDeCelda(Posicion posicion)
        {
            int indiceX = posicion.getX() / tamCelda;
            int indiceY = posicion.getY() / tamCelda;
            return new Posicion(indiceX * tamCelda + tamCelda / 2, indiceY * tamCelda + tamCelda / 2);
        }
        public Posicion ObtenerSalida() {
            return Salida;
        }

        public void agregarObstaculo(IColisionable objeto)
        {
            Obstaculos.Add(objeto);
        }

        public Celda obtenerCelda(int indiceX, int indiceY)
        {
            return grilla[indiceX, indiceY];
        }

        public void removerObstaculo(IColisionable objeto)
        {
            Obstaculos.Remove(objeto);
        }
    }          
}
